﻿using System;
using System.Collections.Specialized;
using System.Text;
using System.Web.Profile;
using System.Configuration;
using System.DirectoryServices;
using System.Web;

namespace ADAMComponents
{
    /// <summary>
    /// Perfil de usuario a partir de un ADAM.
    /// Solo funciona con conexiones no seguras.
    /// Admite los siguientes parámetros:
    ///     connectionStringName    : Nombre de la cadena de conexión con ADAM almacenada en la sección de Connectionstrings del Web.config. 
    ///                                 Debe tener un formato similar al siguiente: LDAP://host:389/DC=Portal,DC=COM
    ///     userName                : (opcional)Nombre del usuario para conectar con ADAM. En caso de indicarse debe indicarse el parámetro "password".
    ///     password                : (Opcional si no se especifica userName). Password del usuario para conectar con ADAM.
    ///     attributeMapUsername    : (Opcional). Atributo del ADAM que identifica el nombre del usuario. Por defecto busca en el atributo "userPrincipalName".
    ///     
    /// </summary>
    public class ADAMProfileProvider : ProfileProvider
    {
        /// <summary>
        /// Indica que se ha incializado correctamente
        /// </summary>
        private bool _Initialized;
        /// <summary>
        /// Cadena de conexión
        /// </summary>
        protected string _ConnectionString;
        /// <summary>
        /// Usuario de conexión
        /// </summary>
        protected string _username;
        /// <summary>
        /// Password de conexión
        /// </summary>
        protected string _password;
        /// <summary>
        /// Nombre la aplicación
        /// </summary>
        protected string _ApplicationName;
        /// <summary>
        /// Atributo del ADAM por el que buscar un usuario
        /// </summary>
        protected string _AttributeMapUsername;
        /// <summary>
        /// Nombre la aplicación
        /// </summary>
        public override string ApplicationName
        {
            get { return this._ApplicationName; }
            set { this._ApplicationName = value; }
        }


        /// <summary>
        /// Devuelve una entrada al ADAM
        /// </summary>
        /// <returns></returns>
        private DirectoryEntry getADAMEntry()
        {
             DirectoryEntry user = null;
            if(string.IsNullOrEmpty(this._username))
                user = new DirectoryEntry(this._ConnectionString,
                  null,
                  null,
                  AuthenticationTypes.Secure
                  );
            else
                user = new DirectoryEntry(this._ConnectionString,
                  this._username,
                  this._password,
                  AuthenticationTypes.Secure
                  );

            return user;
        }


        public ADAMProfileProvider()
        {
            this._Initialized = false;
            this._ConnectionString = string.Empty;
            this._username = string.Empty;
            this._password = string.Empty;
            this._ApplicationName = string.Empty;
            this._AttributeMapUsername = string.Empty;
        }

        /// <summary>
        /// Inicializa el proveedor.
        /// En caso de no estar inicializado correctamente lanza una excepción indicando el problema.
        /// </summary>
        /// <param name="name">Nombre del proveedor</param>
        /// <param name="config">Colección de parámetros de configuración del provider</param>
        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null)
                throw new NullReferenceException("ADAMProfileProvider.Initialize.Initialize: Configuración vacía");

            if (string.IsNullOrEmpty(config["connectionStringName"]))
                throw new ArgumentException("ADAMProfileProvider.Initialize.Initialize: Indique el nombre de la cadena de conexión");

            if(ConfigurationManager.ConnectionStrings[config["connectionStringName"]] == null)
                throw new ArgumentException("ADAMProfileProvider.Initialize.Initialize: connectionStringName incorrecto");

            this._ConnectionString = ConfigurationManager.ConnectionStrings[config["connectionStringName"]].ConnectionString;
            if (string.IsNullOrEmpty(_ConnectionString))
                throw new ArgumentException("ADAMProfileProvider.Initialize.Initialize: Falta indicar la cadena de conexión");

            if (string.IsNullOrEmpty(config["userName"]))
            {
                // Impersonate
                this._username = "";
                this._password = "";
            }
            else
            {
                this._username = config["userName"];
                if (string.IsNullOrEmpty(config["password"]))
                    throw new ArgumentException("ADAMProfileProvider.Initialize.Initialize: Debe indicar la contraseña");

                this._password = config["password"];
            }

            if (string.IsNullOrEmpty(config["attributeMapUsername"]))
                this._AttributeMapUsername = "userPrincipalName";
            else
                this._AttributeMapUsername = config["attributeMapUsername"];


            // Indica que el prov eedor se ha inicializado correctamente
            this._Initialized = true;

            // Initialize the abstract base class.
            base.Initialize(name, config);
        }


        /// <summary>
        /// Carga las propiedades del perfil del usuario indicado
        /// </summary>
        /// <param name="context">Contexto del usuario</param>
        /// <param name="collection">Colección de propiedades a leer</param>
        /// <returns>Colección de propiedades leídas con sus valores</returns>
        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
        {
            try
            {
                if (!this._Initialized)
                    throw new TypeInitializationException("ADAMComponents.ADAMProfileProvider", new Exception("Proveedor no inicializado"));

                if (!context.ContainsKey("UserName"))
                    throw new ArgumentException("ADAMProfileProvider.GetPropertyValues: Contexto sin UserName");

                if (string.IsNullOrEmpty((string)context["UserName"]))
                    throw new ArgumentException("ADAMProfileProvider.GetPropertyValues: UserName vacío o null");

                // Conectamos con el ADAM
                DirectoryEntry user = getADAMEntry();

                // Buscamos el usuario
                DirectorySearcher search = new DirectorySearcher(user);
                search.Filter = string.Format("(&(objectClass=user)(cn={0}))", (string)context["UserName"]);
                SearchResult result = search.FindOne();

                SettingsPropertyValueCollection res = new SettingsPropertyValueCollection();
                if (result != null)
                    foreach (SettingsProperty prop in collection)
                    {
                        SettingsPropertyValue info = new SettingsPropertyValue(prop);
                        if (result.Properties[info.Name].Count > 0)
                            info.PropertyValue = result.Properties[info.Name][0];

                        res.Add(info);
                    }

                return res;
            }
            catch (Exception ex)
            {
                try
                {
                    System.Diagnostics.EventLog.WriteEntry("ADAMProfileProvider", ex.Message, System.Diagnostics.EventLogEntryType.Error);
                }
                catch { } 
                throw ex;
            }
        }

        /// <summary>
        /// Modifica las propiedades del perfil del usuario
        /// </summary>
        /// <param name="context">Contexto del usuario</param>
        /// <param name="collection">Colección de propiedades con valores a modificar</param>
        public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
        {
            try
            {
                if (!this._Initialized)
                    throw new TypeInitializationException("ADAMComponents.ADAMProfileProvider", new Exception("Proveedor no inicializado"));

                if (!context.ContainsKey("UserName"))
                    throw new ArgumentException("ADAMProfileProvider.GetPropertyValues: Contexto sin UserName");

                if (string.IsNullOrEmpty((string)context["UserName"]))
                    throw new ArgumentException("ADAMProfileProvider.GetPropertyValues: UserName vacío o null");

                // Conectamos con el ADAM
                DirectoryEntry user = getADAMEntry();

                // Buscamos el usuario
                DirectorySearcher search = new DirectorySearcher(user);
                search.Filter = string.Format("(&(objectClass=user)(cn={0}))", (string)context["UserName"]);
                SearchResult result = search.FindOne();

                if (result != null)
                {
                    DirectoryEntry userFind = result.GetDirectoryEntry();
                    foreach (SettingsPropertyValue prop in collection)
                    {
                        if (userFind.Properties[prop.Name].Count < 1)
                            if (prop.PropertyValue is string)
                            {
                                if (!string.IsNullOrEmpty((string)prop.PropertyValue))
                                    userFind.Properties[prop.Name].Add((string)prop.PropertyValue);
                            }
                            else
                                userFind.Properties[prop.Name].Add(prop.PropertyValue);
                        else
                            userFind.Properties[prop.Name].Value = prop.PropertyValue;
                    }


                    userFind.CommitChanges();
                }
            }
            catch (Exception ex)
            {
                try
                {
                    System.Diagnostics.EventLog.WriteEntry("ADAMProfileProvider", ex.Message, System.Diagnostics.EventLogEntryType.Error);
                }
                catch { }
                throw ex;
            }
        }


        /// <summary>
        /// Obtiene todos los perfiles de usuario disponibles en el repositorio.
        /// Busca por la propiedad userPrincipalName
        /// </summary>
        /// <param name="authenticationOption"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <param name="totalRecords">Número de registros encontrados</param>
        /// <returns></returns>
        public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = 0;
            try
            {
                if (!this._Initialized)
                    throw new TypeInitializationException("ADAMComponents.ADAMProfileProvider", new Exception("Proveedor no inicializado"));

                // Conectamos con el ADAM
                DirectoryEntry user = getADAMEntry();

                // Buscamos el usuario
                DirectorySearcher search = new DirectorySearcher(user);
                search.Filter = "(&(objectClass=user))";
                SearchResultCollection results = search.FindAll();

                ProfileInfoCollection res = new ProfileInfoCollection();
                if (results != null)
                    foreach (SearchResult userFilter in results)
                    {
                        if (userFilter.Properties[this._AttributeMapUsername].Count > 0)
                        {
                            ProfileInfo userProfileInfo = new ProfileInfo((string)(userFilter.Properties[this._AttributeMapUsername][0]), false, DateTime.MinValue, DateTime.MinValue, 0);
                            res.Add(userProfileInfo);
                        }
                    }

                totalRecords = res.Count;
                return res;
            }
            catch (Exception ex)
            {
                try
                {
                    System.Diagnostics.EventLog.WriteEntry("ADAMProfileProvider", ex.Message, System.Diagnostics.EventLogEntryType.Error);
                }
                catch { } 
                throw ex;
            }
        }

        // Summary:
        //     When overridden in a derived class, deletes all user-profile data for profiles
        //     in which the last activity date occurred before the specified date.
        //
        // Parameters:
        //   authenticationOption:
        //     One of the System.Web.Profile.ProfileAuthenticationOption values, specifying
        //     whether anonymous, authenticated, or both types of profiles are deleted.
        //
        //   userInactiveSinceDate:
        //     A System.DateTime that identifies which user profiles are considered inactive.
        //     If the System.Web.Profile.ProfileInfo.LastActivityDate value of a user profile
        //     occurs on or before this date and time, the profile is considered inactive.
        //
        // Returns:
        //     The number of profiles deleted from the data source.
        public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {
            return 0;
        }

        //
        // Summary:
        //     When overridden in a derived class, deletes profile properties and information
        //     for the supplied list of profiles.
        //
        // Parameters:
        //   profiles:
        //     A System.Web.Profile.ProfileInfoCollection of information about profiles
        //     that are to be deleted.
        //
        // Returns:
        //     The number of profiles deleted from the data source.
        public override int DeleteProfiles(ProfileInfoCollection profiles)
        {
            return 0;
        }

        //
        // Summary:
        //     When overridden in a derived class, deletes profile properties and information
        //     for profiles that match the supplied list of user names.
        //
        // Parameters:
        //   usernames:
        //     A string array of user names for profiles to be deleted.
        //
        // Returns:
        //     The number of profiles deleted from the data source.
        public override int DeleteProfiles(string[] usernames)
        {
            return 0;
        }


        //
        // Summary:
        //     When overridden in a derived class, retrieves profile information for profiles
        //     in which the last activity date occurred on or before the specified date
        //     and the user name matches the specified user name.
        //
        // Parameters:
        //   authenticationOption:
        //     One of the System.Web.Profile.ProfileAuthenticationOption values, specifying
        //     whether anonymous, authenticated, or both types of profiles are returned.
        //
        //   usernameToMatch:
        //     The user name to search for.
        //
        //   userInactiveSinceDate:
        //     A System.DateTime that identifies which user profiles are considered inactive.
        //     If the System.Web.Profile.ProfileInfo.LastActivityDate value of a user profile
        //     occurs on or before this date and time, the profile is considered inactive.
        //
        //   pageIndex:
        //     The index of the page of results to return.
        //
        //   pageSize:
        //     The size of the page of results to return.
        //
        //   totalRecords:
        //     When this method returns, contains the total number of profiles.
        //
        // Returns:
        //     A System.Web.Profile.ProfileInfoCollection containing user profile information
        //     for inactive profiles where the user name matches the supplied usernameToMatch
        //     parameter.
        public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = 0;
            return new ProfileInfoCollection();
        }


        //
        // Summary:
        //     When overridden in a derived class, retrieves profile information for profiles
        //     in which the user name matches the specified user names.
        //
        // Parameters:
        //   authenticationOption:
        //     One of the System.Web.Profile.ProfileAuthenticationOption values, specifying
        //     whether anonymous, authenticated, or both types of profiles are returned.
        //
        //   usernameToMatch:
        //     The user name to search for.
        //
        //   pageIndex:
        //     The index of the page of results to return.
        //
        //   pageSize:
        //     The size of the page of results to return.
        //
        //   totalRecords:
        //     When this method returns, contains the total number of profiles.
        //
        // Returns:
        //     A System.Web.Profile.ProfileInfoCollection containing user-profile information
        //     for profiles where the user name matches the supplied usernameToMatch parameter.
        public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = 0;
            return new ProfileInfoCollection();
        }


        //
        // Summary:
        //     When overridden in a derived class, retrieves user-profile data from the
        //     data source for profiles in which the last activity date occurred on or before
        //     the specified date.
        //
        // Parameters:
        //   authenticationOption:
        //     One of the System.Web.Profile.ProfileAuthenticationOption values, specifying
        //     whether anonymous, authenticated, or both types of profiles are returned.
        //
        //   userInactiveSinceDate:
        //     A System.DateTime that identifies which user profiles are considered inactive.
        //     If the System.Web.Profile.ProfileInfo.LastActivityDate of a user profile
        //     occurs on or before this date and time, the profile is considered inactive.
        //
        //   pageIndex:
        //     The index of the page of results to return.
        //
        //   pageSize:
        //     The size of the page of results to return.
        //
        //   totalRecords:
        //     When this method returns, contains the total number of profiles.
        //
        // Returns:
        //     A System.Web.Profile.ProfileInfoCollection containing user-profile information
        //     about the inactive profiles.
        public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = 0;
            return new ProfileInfoCollection();
        }



        //
        // Summary:
        //     When overridden in a derived class, returns the number of profiles in which
        //     the last activity date occurred on or before the specified date.
        //
        // Parameters:
        //   authenticationOption:
        //     One of the System.Web.Profile.ProfileAuthenticationOption values, specifying
        //     whether anonymous, authenticated, or both types of profiles are returned.
        //
        //   userInactiveSinceDate:
        //     A System.DateTime that identifies which user profiles are considered inactive.
        //     If the System.Web.Profile.ProfileInfo.LastActivityDate of a user profile
        //     occurs on or before this date and time, the profile is considered inactive.
        //
        // Returns:
        //     The number of profiles in which the last activity date occurred on or before
        //     the specified date.
        public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {
            return 0;
        }

    }
}
